/*
 * ArCoLIVE - Live Internet Videoconference Environment
 * Information Technology Departament - Federal University of Alagoas - Brazil
 *
 * Copyright (C) 2005 Leandro Melo de Sales (leandro@labpesquisas.tci.ufal.br)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package br.ufal.tci.nexos.arcolive.service.conference;

import java.net.InetAddress;

import javax.media.Controller;
import javax.media.Format;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.Processor;
import javax.media.control.TrackControl;
import javax.media.format.VideoFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.PushBufferDataSource;
import javax.media.protocol.PushBufferStream;
import javax.media.rtp.RTPManager;
import javax.media.rtp.SendStream;
import javax.media.rtp.SessionAddress;

import br.ufal.tci.nexos.arcolive.exception.ArCoLIVECannotCreateTransmitterException;
import br.ufal.tci.nexos.arcolive.exception.ArCoLIVEException;
import br.ufal.tci.nexos.arcolive.logger.ArCoLIVELogRepository;
import br.ufal.tci.nexos.arcolive.participant.ArCoLIVEParticipant;
import br.ufal.tci.nexos.arcolive.participant.ArCoLIVEParticipantGroup;


/**
 * CLASSNAME.java
 *
 * CLASS DESCRIPTION
 *
 * @see CLASSNAME
 *
 * @author <a href="mailto:felipe@labpesquisas.tci.ufal.br">Felipe Barros Pontes</a>.
 * @author <a href="mailto:leandro@labpesquisas.tci.ufal.br">Leandro Melo de Sales</a>.
 * @since 0.1
 * @version 0.1
 *
 * <p><b>Revisions:</b>
 *
 * <p><b>yyyymmdd USERNAME:</b>
 * <ul>
 * <li> VERSION
 * </ul>
 */
public class Conference0neToManyImpl {
	private int rtpPort;

	private boolean streamCreated = false;
	
	//JMF Attributes
	private MediaLocator sourceLocator;
	private DataSource datasource;
	private Processor processor = null;
	private DataSource dataOutput = null;
    private RTPManager rtpMgrs[];
	
	public Conference0neToManyImpl(MediaLocator sourceLocator, int rtpPort) {
		this.sourceLocator = sourceLocator;
		this.rtpPort = rtpPort;
	}
	
    public void createProcessor() throws ArCoLIVEException {
		if (this.sourceLocator == null) {
		    throw new ArCoLIVEException("Locator is null!");
		}

		try {
			this.datasource = Manager.createDataSource(this.sourceLocator);
		    this.processor = Manager.createProcessor(this.datasource);
		} catch (Exception e) {
			throw new ArCoLIVEException("Error while trying to create datasource/processor", e);
		} 

		// Wait for it to configure
		boolean result = ConferenceUtil.waitForState(processor, Processor.Configured);
		if (result == false) {
		    throw new ArCoLIVEException("Couldn't configure processor");
		}

		// Get the tracks from the processor
		TrackControl [] tracks = processor.getTrackControls();
		// Do we have atleast one track?
		if (tracks == null || tracks.length < 1)
		    throw new ArCoLIVEException("Couldn't find tracks in processor");

		// Set the output content descriptor to RAW_RTP
		// This will limit the supported formats reported from
		// Track.getSupportedFormats to only valid RTP formats.
		ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP);
		this.processor.setContentDescriptor(cd);

		Format supported[];
		Format chosen;
		boolean atLeastOneTrack = false;

		// Program the tracks.
		for (int i = 0; i < tracks.length; i++) {
		    Format format = tracks[i].getFormat();
		    if (tracks[i].isEnabled()) {
		    	supported = tracks[i].getSupportedFormats();

		    	// We've set the output content to the RAW_RTP.
		    	// So all the supported formats should work with RTP.
		    	// We'll just pick the first one.

		    	if (supported.length > 0) {
		    		if (supported[0] instanceof VideoFormat) {
		    		// For video formats, we should double check the
		    		// sizes since not all formats work in all sizes.
		    			chosen = ConferenceUtil.checkForVideoSizes(format, supported[0]);
		    		} else {
		    			chosen = supported[0];
		    		}
		    		tracks[i].setFormat(chosen);
		    		ArCoLIVELogRepository.getInstance().log("Track " + i + " is set to transmit as:");
		    		ArCoLIVELogRepository.getInstance().log("  " + chosen);
		    		atLeastOneTrack = true;
		    	} else {
		    		tracks[i].setEnabled(false);
		    	}
		    } else {
		    	tracks[i].setEnabled(false);
		    }

		    if (!atLeastOneTrack) {
		    	throw new ArCoLIVEException("Couldn't set any of the tracks to a valid RTP format");
		    }

		    // Realize the processor. This will internally create a flow
		    // graph and attempt to create an output datasource for JPEG/RTP
		    // audio frames.
		    result = ConferenceUtil.waitForState(processor, Controller.Realized);
		    if (result == false) {
		    	throw new ArCoLIVEException("Couldn't realize processor");
		    }
		}

		// Set the JPEG quality to .5.
		ConferenceUtil.setJPEGQuality(processor, 0.5f);

		// Get the output data source of the processor
		this.dataOutput = processor.getDataOutput();

    }


    /**
     * Use the RTPManager API to create sessions for each media 
     * track of the processor.
     */
    public void createTransmitter() throws ArCoLIVECannotCreateTransmitterException {

		// Cheated.  Should have checked the type.
		PushBufferDataSource pbds = (PushBufferDataSource)dataOutput;
		PushBufferStream pbss[] = pbds.getStreams();

		rtpMgrs = new RTPManager[pbss.length];
		SessionAddress localAddr;//, destAddr;
		int port;
		//SourceDescription srcDesList[];

		//int portRTP = Integer.parseInt(this.getAttribute("portRTP"));
		//int portRTCP = Integer.parseInt(this.getAttribute("portRTCP"));
		
		for (int i = 0; i < pbss.length; i++) {
		    try {
				rtpMgrs[i] = RTPManager.newInstance();	    
	
				// The local session address will be created on the
				// same port as the the target port. This is necessary
				// if you use AVTransmit2 in conjunction with JMStudio.
				// JMStudio assumes -  in a unicast session - that the
				// transmitter transmits from the same port it is receiving
				// on and sends RTCP Receiver Reports back to this port of
				// the transmitting host.
				
				port = this.rtpPort + 2*i;
				localAddr = new SessionAddress(InetAddress.getLocalHost(), port);
				rtpMgrs[i].initialize(localAddr);
				
				ArCoLIVELogRepository.getInstance().log(localAddr);
				
		    } catch (Exception  e) {
		    	e.printStackTrace();
		    	throw new ArCoLIVECannotCreateTransmitterException("Error while trying to create transmiter", e);
		    }
		}
    }

	public Processor getProcessor() {
		return this.processor;
	}
	
	public void stop() {
		for (int i = 0; i < this.rtpMgrs.length; i++) {
			rtpMgrs[i].removeTargets("Session ended for RTCP Stream.");
		}
		
		synchronized (this) {
		    if (processor != null) {
				processor.stop();
				processor.close();
				processor = null;
				for (int i = 0; i < rtpMgrs.length; i++) {
				    rtpMgrs[i].removeTargets( "Session ended.");
				    rtpMgrs[i].dispose();
				}
		    }
		}
	}
	
	public void addTarget(ArCoLIVEParticipant participant, ArCoLIVEParticipantGroup whichGroup) throws ArCoLIVEException {
		SendStream sendStream;
		int port;
    	try {
    		for (int i = 0; i < this.rtpMgrs.length; i++) {
    			port = this.rtpPort + 2*i;
				this.rtpMgrs[i].addTarget(participant.createSessionAddressRTP(port));
    			if (whichGroup.getSize() == 1 && (!this.streamCreated)) {
    				sendStream = rtpMgrs[i].createSendStream(this.dataOutput, i);		
    				sendStream.start();
    			}
			}
    		this.streamCreated = true;
    		
		} catch (Exception e) {
			throw new ArCoLIVEException(e);
		}
	}
	
	public int getPort() {
		return this.rtpPort;
	}
	
}
